package cache

import (
    "bytes"
    "encoding/json"
    "errors"
    "fmt"
    "reflect"
)

var NotSliceErr = errors.New("input type is not a slice")
var NotPtrErr = errors.New("input type is not a slice ptr")

// ----------------------------------------
//  Redis List
// ----------------------------------------
type RedisList struct {
    client *RedisClient
    key    string
}

func CreateRedisList(client *RedisClient, key string, args ...interface{}) *RedisList {
    if len(args) > 0 {
        key = fmt.Sprintf(key, args...)
    }
    return &RedisList{client: client, key: key}
}

func (list *RedisList) LRang(o interface{}) error {
    if data, err := list.client.LRange(list.key, 0, -1).Result(); err != nil {
        return err
    } else {
        ptrType := reflect.TypeOf(o)
        if ptrType.Kind() != reflect.Ptr {
            return NotPtrErr
        }
        if ptrType.Elem().Kind() != reflect.Slice {
            return NotSliceErr
        }
        return stringSliceToObjSlice(data, o)
    }
}

func (list *RedisList) RRang(o interface{}) error {
    if data, err := list.client.LRange(list.key, 0, -1).Result(); err != nil {
        return err
    } else {
        ptrType := reflect.TypeOf(o)
        if ptrType.Kind() != reflect.Ptr {
            return NotPtrErr
        }
        if ptrType.Elem().Kind() != reflect.Slice {
            return NotSliceErr
        }
        return stringSliceToObjSlice(data, o, 1)
    }
}

func (list *RedisList) LPop(o interface{}) error {
    if data, err := list.client.LPop(list.key).Result(); err != nil {
        return err
    } else {
        return stringToObj(data, o)
    }
}

func (list *RedisList) RPop(o interface{}) error {
    if data, err := list.client.RPop(list.key).Result(); err != nil {
        return err
    } else {
        return stringToObj(data, o)
    }
}

func (list *RedisList) LIndex(index int64, o interface{}) error {
    if data, err := list.client.LIndex(list.key, index).Result(); err != nil {
        return err
    } else {
        return stringToObj(data, o)
    }
}

func stringToObj(src string, target interface{}) error {
    ptrType := reflect.TypeOf(target)
    if ptrType.Kind() != reflect.Ptr {
        return NotPtrErr
    }
    if ptrType.Elem().Kind() == reflect.String {
        target = &src
        return nil
    }
    return json.Unmarshal([]byte(src), target)
}

func stringSliceToObjSlice(src []string, target interface{}, order ...int) error {
    jsonBytes := bytes.Buffer{}
    jsonBytes.WriteString("[")

    if len(order) > 0 {
        for idx := len(src) - 1; idx >= 0; idx-- {
            jsonBytes.WriteString(src[idx])
            if idx > 0 {
                jsonBytes.WriteString(",")
            }
        }
    } else {
        for idx, str := range src {
            jsonBytes.WriteString(str)
            if idx < len(src)-1 {
                jsonBytes.WriteString(",")
            }
        }
    }

    jsonBytes.WriteString("]")
    return json.Unmarshal(jsonBytes.Bytes(), target)
}
